﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Security.Principal;
using System.Threading;
using Common.Logging;
using System.Globalization;
using Common;

namespace WebServer
{
    /// <summary>
    /// A simple web server built on top of the HttpListener class
    /// </summary>
    public class HttpServer : IServer<IHttpRequestHandler>
    {
        private static readonly ILog log = LogManager.GetCurrentClassLogger();

        private int _port;
        private Thread _keepAlive = null;

        /// <summary>
        /// Constructs a server that listens on a specific port but does not start right away
        /// </summary>
        /// <param name="port">The port to listen on</param>
        public HttpServer(int port)
        {
            _port = port;
            init();
        }

        private void init()
        {
            _keepAlive = new Thread(KeepAlive);
            _keepAlive.IsBackground = true;
            _keepAlive.Start();
        }

        /// <summary>
        /// Prevents the .Net application from exiting
        /// </summary>
        /// <remarks>
        /// .Net automatically exits when all _non_ background threads have stopped.  
        /// By toggling this one thread as background/foreground we can prevent the 
        /// application from exiting without needing hooks at other parts of the Application.
        /// Keep in mind this only prevents the Application from exiting when running, it 
        /// does not prevent the application from exiting when stopped, nor does it have
        /// any ability to stop the application if another thread is being used elsewhere.
        /// </remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage ("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void KeepAlive()
        {
            while (true)
            {
                try { Thread.Sleep(5000); }
                catch (Exception) { }
            }
        }

        /// <summary>
        /// Starts the server
        /// </summary>
        public void Start()
        {
            try
            {
                _running = true;
                _keepAlive.IsBackground = false;
                foreach (HttpListener listener in _listenersToHandlers.Keys)
                {
                    if (!listener.IsListening)
                    {
                        listener.Start();
                        BeginGetContext(listener);
                    }
                }
            }
            catch (HttpListenerException hle)
            {
                if (hle.ErrorCode == 5)
                {
                    throw new ElevatedPermissionRequiredException(hle);
                }
            }
        }

        /// <summary>
        /// Stops the server
        /// </summary>
        public void Stop()
        {
            _running = false;
            _keepAlive.IsBackground = true;
            foreach (HttpListener listener in _listenersToHandlers.Keys)
            {
                listener.Stop();
                listener.Abort();
                
            }            
        }

        private bool _running = false;

        private List<String> _prefixes = new List<string>();
        private Dictionary<IHttpRequestHandler, HttpListener> _handlersToListeners = new Dictionary<IHttpRequestHandler, HttpListener>();
        private Dictionary<HttpListener, IHttpRequestHandler> _listenersToHandlers = new Dictionary<HttpListener, IHttpRequestHandler>();
        /// <summary>
        /// Adds a handler for the root address
        /// </summary>
        /// <param name="handler">The handler to add</param>
        public void AddDefaultHandler(IHttpRequestHandler handler)
        { AddHandler(null, handler); }
        /// <summary>
        /// Adds a handler for a specific address
        /// </summary>
        /// <param name="prefix">The address prefix to listen for</param>
        /// <param name="handler">The handler to add</param>
        public void AddHandler(string prefix, IHttpRequestHandler handler)
        {
            String cleanedPrefix = prefix ?? "";
            if (!cleanedPrefix.ToUpperInvariant().StartsWithInvariant("http://") && !cleanedPrefix.ToUpperInvariant().StartsWithInvariant("https://"))
            { cleanedPrefix = "http://*:" + _port + "/" + cleanedPrefix; }
            if (!cleanedPrefix.EndsWithInvariant("/"))
            { cleanedPrefix += "/"; }
            lock (_handlersToListeners)
            {
                if (_prefixes.Contains(cleanedPrefix))
                { throw new ArgumentException("duplicate prefix.  " + cleanedPrefix); }
                HttpListener listener = null;
                if (!_handlersToListeners.TryGetValue(handler, out listener))
                {
                    listener = new HttpListener();
                    _handlersToListeners.Add(handler, listener);
                    _listenersToHandlers.Add(listener, handler);
                }
                listener.Prefixes.Add(cleanedPrefix);
                _prefixes.Add(cleanedPrefix);
                if (_running && !listener.IsListening)
                {
                    listener.Start();
                    BeginGetContext(listener);
                }
            }
        }

        private void BeginGetContext(HttpListener listener)
        {
            if (_running && listener.IsListening)
            { listener.BeginGetContext(EndGetContext, listener); }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void EndGetContext(IAsyncResult result)
        {
            HttpListener listener = result.AsyncState as HttpListener;
            HttpListenerContext context = null;
            try { context = listener.EndGetContext(result); }
            catch (Exception e) { log.Error(e.Message); }
            finally
            { BeginGetContext(listener); }
            System.Threading.ThreadPool.QueueUserWorkItem(f =>
            {
                try
                {
                    IHttpRequestHandler handler = _listenersToHandlers[listener];
                    if (handler == null)
                    {
                        context.Response.StatusCode = 404;
                        context.Response.StatusDescription = "No handler found for your request";
                    }
                    else
                    {
                        context.Response.StatusCode = 200;
                        context.Response.StatusDescription = "OK";

                        String method = context.Request.HttpMethod.ToUpperInvariant().Trim();
                        try
                        {
                            if (method == "GET")
                            { handler.Get(context.Request, context.Response, context.User); }
                            if (method == "PUT")
                            { handler.Put(context.Request, context.Response, context.User); }
                            if (method == "POST")
                            { handler.Post(context.Request, context.Response, context.User); }
                            if (method == "DELETE")
                            { handler.Delete(context.Request, context.Response, context.User); }
                        }
                        catch (Exception e)
                        {
                            context.Response.StatusCode = 500;
                            log.Error(e);
                        }
                        context.Response.OutputStream.Flush();
                        context.Response.Close();
                    }
                }
                catch (Exception e2)
                { log.Error(e2); }
            });
        }
    }
}
